package logger

import (
	"context"
	"github.com/go-kratos/kratos/v2/log"
	"github.com/go-kratos/kratos/v2/middleware"
	"github.com/go-kratos/kratos/v2/transport"
	"os"
	"sync"
)

var (
	logger     log.Logger
	loggerOnce sync.Once
)

const (
	XRequestId  = "X-REQUEST-ID"
	XUserId     = "User-Id"
	XRealName   = "Real-Name"
	XUserName   = "User-Name"
	XGroupCodes = "Group-Codes"
)

// Option 定义了一个函数类型，用于设置 kv 的选项
type Option func(*[]interface{})

// WithServiceID 是一个设置 service.id 的选项
func WithServiceID(id string) Option {
	return func(kv *[]interface{}) {
		*kv = append(*kv, "service.id", id)
	}
}

// WithServiceName 是一个设置 service.name 的选项
func WithServiceName(name string) Option {
	return func(kv *[]interface{}) {
		*kv = append(*kv, "service.name", name)
	}
}

// WithServiceVersion 是一个设置 service.version 的选项
func WithServiceVersion(version string) Option {
	return func(kv *[]interface{}) {
		*kv = append(*kv, "service.version", version)
	}
}

// WithTraceId 是一个设置 trace.id 的选项
func WithTraceId() Option {
	return func(kv *[]interface{}) {
		*kv = append(*kv, "trace.id", getTraceId())
	}
}

// WithUserId 是一个设置 user.id 的选项
func WithUserId() Option {
	return func(kv *[]interface{}) {
		*kv = append(*kv, "user.id", getUserId())
	}
}

// WithUserName 是一个设置 user.name 的选项
func WithUserName() Option {
	return func(kv *[]interface{}) {
		*kv = append(*kv, "user.name", getUserName())
	}
}

// WithUserGroup 是一个设置 user.group 的选项
func WithUserGroup() Option {
	return func(kv *[]interface{}) {
		*kv = append(*kv, "user.group", getUserGroup())
	}
}

func NewLogger(filePath string, options ...Option) log.Logger {
	loggerOnce.Do(func() {
		// logger writer
		writeIO := os.Stdout
		if filePath != "" {
			f, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
			if err != nil {
				panic("打开日志失败，注意读写权限：" + filePath)
			}
			writeIO = f
		}

		// logger filter level
		logLevel := log.ParseLevel(os.Getenv("LOG_LEVEL"))

		// logger init
		kv := []interface{}{
			"ts", log.DefaultTimestamp,
			"caller", log.DefaultCaller,
		}
		for _, o := range options {
			o(&kv)
		}
		logger = log.With(log.NewFilter(log.NewStdLogger(writeIO), log.FilterLevel(logLevel)), kv...)

		// set global logger filter level
		log.SetLogger(log.NewFilter(logger, log.FilterLevel(logLevel)))
	})

	return logger
}

func With(lg log.Logger, options ...Option) log.Logger {
	kv := make([]interface{}, 0, len(options))
	for _, o := range options {
		o(&kv)
	}
	return log.With(lg, kv...)
}

func NewHelper(logger log.Logger, kv ...interface{}) *log.Helper {
	if len(kv) == 1 {
		kv = []interface{}{"module", kv[0]}
	}
	return log.NewHelper(log.With(logger, kv...))
}

func getTraceId() log.Valuer {
	return func(ctx context.Context) interface{} {
		if ctx == nil {
			return ""
		}
		rq, ok := transport.FromServerContext(ctx)
		if !ok {
			return ""
		}
		return rq.RequestHeader().Get(XRequestId)
	}
}

func getUserId() log.Valuer {
	return func(ctx context.Context) interface{} {
		if ctx == nil {
			return ""
		}
		rq, ok := transport.FromServerContext(ctx)
		if !ok {
			return ""
		}
		return rq.RequestHeader().Get(XUserId)
	}
}

func getUserName() log.Valuer {
	return func(ctx context.Context) interface{} {
		if ctx == nil {
			return ""
		}
		rq, ok := transport.FromServerContext(ctx)
		if !ok {
			return ""
		}
		userName := rq.RequestHeader().Get(XRealName)
		if userName == "" {
			userName = rq.RequestHeader().Get(XUserName)
		}
		return userName
	}
}

func getUserGroup() log.Valuer {
	return func(ctx context.Context) interface{} {
		if ctx == nil {
			return ""
		}
		rq, ok := transport.FromServerContext(ctx)
		if !ok {
			return ""
		}
		return rq.RequestHeader().Get(XGroupCodes)
	}
}

func SetTraceId() middleware.Middleware {
	return func(handler middleware.Handler) middleware.Handler {
		return func(ctx context.Context, req interface{}) (reply interface{}, err error) {
			cc, _ := transport.FromClientContext(ctx)
			if tr, ok := transport.FromServerContext(ctx); ok {
				cc.RequestHeader().Add(XRequestId, tr.RequestHeader().Get(XRequestId))
			}
			return handler(ctx, req)
		}
	}
}
